package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.atguigu.gmall.search.service.SearchService;
import com.atguigu.gmall.search.vo.Goods;
import com.atguigu.gmall.search.vo.SearchAttrValueResponseVo;
import com.atguigu.gmall.search.vo.SearchParamVo;
import com.atguigu.gmall.search.vo.SearchResponseVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.query.HighlightQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author zhaoshuo
 * @ClassName SearchServiceImpl
 * @Description
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public SearchResponseVo search(SearchParamVo paramVo) {
        try {
            SearchRequest searchRequest = getSearchRequest(paramVo);
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            SearchResponseVo searchResponseVo = new SearchResponseVo();
            searchResponseVo.setPageNum(paramVo.getPageNum());
            searchResponseVo.setPageSize(paramVo.getPageSize());
            //1、查询文档数据
            SearchHits hits = response.getHits();
            Long total = hits.getTotalHits().value;
            searchResponseVo.setTotal(total);
            SearchHit[] searchHits = hits.getHits();
            if (ArrayUtils.isNotEmpty(searchHits)) {
                ObjectMapper mapper = new ObjectMapper();
                List<Goods> goodsList = new ArrayList<>();
                for (SearchHit searchHit : searchHits) {
                    Map<String, Object> map = searchHit.getSourceAsMap();
                    Goods goods = mapper.convertValue(map, Goods.class);
                    //高亮
                    HighlightField field = searchHit.getHighlightFields().get("title");
                    if (field != null && field.getFragments().length > 0) {
                        String highLightTitle = field.getFragments()[0].toString();
                        goods.setTitle(highLightTitle);
                    }
                    goodsList.add(goods);
                }
                searchResponseVo.setGoodsList(goodsList);

            }
            //2、聚合：品牌 分类 检索属性和值
            Map<String, Aggregation> map = response.getAggregations().asMap();
            ParsedLongTerms brandAgg = (ParsedLongTerms) map.get("brandAgg");

            List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
            List<BrandEntity> brandEntities = new ArrayList<>();
            for (Terms.Bucket bucket : brandAggBuckets) {
                BrandEntity brandEntity = new BrandEntity();
                String brandId = bucket.getKey().toString();
                ParsedStringTerms brandNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("brandNameAgg");
                String brandName = brandNameAgg.getBuckets().get(0).getKey().toString();
                ParsedStringTerms brandLogoAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("brandLogoAgg");
                String brandLogo = brandLogoAgg.getBuckets().get(0).getKey().toString();
                brandEntity.setLogo(brandLogo);
                brandEntity.setId(Long.parseLong(brandId));
                brandEntity.setName(brandName);
                brandEntities.add(brandEntity);
            }
            searchResponseVo.setBrands(brandEntities);
            ParsedLongTerms cateAgg = (ParsedLongTerms) map.get("cateAgg");
            List<? extends Terms.Bucket> cateAggBuckets = cateAgg.getBuckets();

            List<CategoryEntity> categories = new ArrayList<>();
            for (Terms.Bucket bucket : cateAggBuckets) {
                CategoryEntity categoryEntity = new CategoryEntity();
                String cateId = bucket.getKey().toString();
                ParsedStringTerms categoryNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("categoryNameAgg");
                String cateName = categoryNameAgg.getBuckets().get(0).getKey().toString();
                categoryEntity.setId(Long.parseLong(cateId));
                categoryEntity.setName(cateName);
                categories.add(categoryEntity);
            }
            searchResponseVo.setCategories(categories);
            //检索属性和值
            ParsedNested searchAttrAgg = (ParsedNested) map.get("searchAttrAgg");
            //获取嵌套属性的子聚合
            ParsedLongTerms attrIdAgg = (ParsedLongTerms) searchAttrAgg.getAggregations().asMap().get("attrIdAgg");
            List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
            List<SearchAttrValueResponseVo> searchAttrValueResponseVos = new ArrayList<>();
            for (Terms.Bucket bucket : attrIdAggBuckets) {
                String attrId = bucket.getKey().toString();
                //每个attrId对应一个attrName
                ParsedStringTerms attrNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("attrNameAgg");
                String attrName = attrNameAgg.getBuckets().get(0).getKey().toString();
                ParsedStringTerms attrValueAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("attrValueAgg");
                List<String> attrValues = new ArrayList<>();
                for (Terms.Bucket attrValueAggBucket : attrValueAgg.getBuckets()) {
                    //遍历attrValue的桶
                    String attrValue = attrValueAggBucket.getKey().toString();
                    attrValues.add(attrValue);
                }
                SearchAttrValueResponseVo searchAttrValueResponseVo = new SearchAttrValueResponseVo();
                searchAttrValueResponseVo.setAttrId(Long.parseLong(attrId));
                searchAttrValueResponseVo.setAttrName(attrName);
                searchAttrValueResponseVo.setAttrValues(attrValues);

                searchAttrValueResponseVos.add(searchAttrValueResponseVo);
            }
            searchResponseVo.setAttrValues(searchAttrValueResponseVos);
            return searchResponseVo;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }

    public SearchRequest getSearchRequest(SearchParamVo paramVo) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        String keyword = paramVo.getKeyword();
        if (StringUtils.isNotBlank(keyword)) {
            boolQuery.must(QueryBuilders.matchQuery("title", keyword));
        }

        Boolean store = paramVo.getStore();
        if (store != null) {
            boolQuery.filter(QueryBuilders.termQuery("store", store));
        }

        Double priceFrom = paramVo.getPriceFrom();
        Double priceTo = paramVo.getPriceTo();
        if (priceFrom != null || priceTo != null) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");

            if (priceFrom != null) {
                rangeQuery.gte(priceFrom);
            }

            if (priceTo != null) {
                rangeQuery.lte(priceTo);
            }
            boolQuery.filter(rangeQuery);
        }

        List<Long> brandIds = paramVo.getBrandId();
        if (!CollectionUtils.isEmpty(brandIds)) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", brandIds));
        }

        Long cid = paramVo.getCid();
        if (cid != null) {
            boolQuery.filter(QueryBuilders.termQuery("categoryId", cid));
        }

        List<String> props = paramVo.getProps();
        if (!CollectionUtils.isEmpty(props)) {
            for (String prop : props) {
                String[] attr = StringUtils.split(prop, ":");

                if (ArrayUtils.isNotEmpty(attr) && attr.length == 2) {
                    String attrId = attr[0];
                    String[] attrValues = StringUtils.split(attr[1], "-");

                    BoolQueryBuilder query = QueryBuilders.boolQuery();
                    query.must(QueryBuilders.termQuery("searchAttrValues.attrId", attrId));
                    query.must(QueryBuilders.termsQuery("searchAttrValues.attrValue", attrValues));
                    boolQuery.filter(QueryBuilders.nestedQuery("searchAttrValues", query, ScoreMode.None));
                }
            }
        }
        sourceBuilder.query(boolQuery);
        sourceBuilder.from((paramVo.getPageNum() - 1) * paramVo.getPageSize());
        sourceBuilder.size(paramVo.getPageSize());

        Integer sort = paramVo.getSort();
        SortBuilder sortBuilder;
        switch (sort) {
            case 1:
                sortBuilder = SortBuilders.fieldSort("price").order(SortOrder.ASC);
                break;
            case 2:
                sortBuilder = SortBuilders.fieldSort("price").order(SortOrder.DESC);
                break;
            case 3:
                sortBuilder = SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
                break;
            case 4:
                sortBuilder = SortBuilders.fieldSort("sales").order(SortOrder.DESC);
                break;
            default:
                sortBuilder = SortBuilders.fieldSort("_score").order(SortOrder.DESC);
        }
        sourceBuilder.sort(sortBuilder);
        sourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<font style='color:red;'>").postTags("</font>"));
        //结果集字段过滤
        sourceBuilder.fetchSource(new String[]{"skuId", "defaultImage", "price", "title", "subtitle"}, null);
        //品牌聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId")
                .subAggregation(AggregationBuilders.terms("brandLogoAgg").field("logo"))
                .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")));
        //分类聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("cateAgg").field("categoryId")
                .subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));
        //嵌套属性
        sourceBuilder.aggregation(AggregationBuilders.nested("searchAttrAgg", "searchAttrValues")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrValues.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrValues.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrValues.attrValue"))));
        System.out.println(sourceBuilder);
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(sourceBuilder);
        return searchRequest;
    }
}